package com.lb.bs.client.factory;

import com.lb.bs.client.annotation.SingletonMark;
import com.lb.bs.client.repository.BeanHolder;
import com.lb.bs.client.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * program: LB-BS-Client
 * author: bsworld.xie
 * create: 2019-12-02 15:16
 * description:
 * 单例工厂，不管参数怎么变，都只在第一次初始化，以后只要是同一个clazz都会返回同一个实例
 *
 * 限制:
 *
 * 1、单例所在class必须只有一个构造器，并且构造完成之后，只会返回这同一个实例
 *
 * 2、必须加上{@link SingletonMark}，用来标记这是个单例类，这样更清晰明了，让人一看就知道是个单例类
 */
public class SingletonFactory<T> {

    private static final Logger logger = LoggerFactory.getLogger(SingletonFactory.class);
    /**
     *
     *
     * 根据class类型构造对象
     *
     * */
    public static <T> T getInstance(Class<T> clazz) {
        BeanHolder<T> holder = BeanHolder.getHolder();
        T t = holder.getBean(clazz);
        if (t == null) {
            synchronized (clazz) {
                try {
                    if (t == null) {
                        check(clazz);
                        t = clazz.newInstance();
                        holder.registerBean(clazz, t);
                        try {//initFlag保留字段，暂时没用
                            Field initFlag = clazz.getDeclaredField("initFlag");
                            initFlag.set(t, true);
                        } catch (NoSuchFieldException e) {
//                            e.printStackTrace();
//                            System.out.println("no such field exception");
                        }
                        invokeCallBack(clazz, t);
                    }
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return t;
    }

    /**
     *
     * 根据构造函数来构造对象
     *
     * */
    public static <T> T getInstanceWithConstructor(Class<T> clazz, Object... parameters) {
        BeanHolder<T> holder = BeanHolder.getHolder();
        T t = holder.getBean(clazz);
        if (t == null) {
            synchronized (clazz) {
                try {
                    if (t == null) {
                        check(clazz);
                        Class<?>[] parameterTypeClazzs = null;
                        if (CollectionUtils.isNotEmpty(parameters)) {
                            boolean multipleFlag = false;
                            Constructor<?>[] constructors = clazz.getConstructors();
                            parameterTypeClazzs = Arrays.stream(parameters).map(v -> v.getClass()).toArray(Class<?>[]::new);
                            for (Constructor<?> constructor : constructors) {
                                Class<?>[] parameterTypes = constructor.getParameterTypes();
                                if (parameterTypes.length == 0) {
                                    continue;
                                }
                                if (parameterTypeClazzs.length == parameterTypes.length) {
                                    for (int i = 0; i < parameterTypeClazzs.length; i++) {
                                        if (!parameterTypeClazzs[i].equals(parameterTypes[i])) {
                                            continue;
                                        }
                                        if (i == parameterTypeClazzs.length - 1) {
                                            multipleFlag = true;
                                        }
                                    }
                                }
                            }
                            if (!multipleFlag) {
                                throw new IllegalStateException("can not find adapter constructor");
                            }
                        }

                        Constructor<T> constructor = null;
                        if (CollectionUtils.isNotEmpty(parameterTypeClazzs)) {
                            constructor = clazz.getConstructor(parameterTypeClazzs);
                        } else {
                            constructor = clazz.getConstructor();
                        }
                        if (CollectionUtils.isNotEmpty(parameters)) {
                            t = constructor.newInstance(parameters);
                        } else {
                            t = constructor.newInstance();
                        }
                        holder.registerBean(clazz, t);
                        invokeCallBack(clazz, t);
                    }
                } catch (Exception e) {
//                   logger.error("getInstanceWithConstructor(), clazz:{}, patameters:{}, error:", new Object[]{clazz, parameters},e);
                    e.printStackTrace();
                }
            }
        }
        return t;
    }


    private static <T> void check(Class<T> clazz) {
        if (clazz == null) {
            throw new IllegalStateException("SingletonFactory.class clazz is null");
        }
        SingletonMark[] annotationsByType = clazz.getAnnotationsByType(SingletonMark.class);
        if (CollectionUtils.isEmpty(annotationsByType)) {
            throw new IllegalStateException("un mark Singleton annotation, clazz:" + clazz);
        }
        Constructor<?>[] constructors = clazz.getConstructors();
        if (CollectionUtils.isEmpty(constructors)) {
            throw new IllegalStateException("clazz constructor can not find , clazz:" + clazz);
        }

        if (constructors.length > 1) {
            throw new IllegalStateException("clazz constructor length more than one , clazz:" + clazz);
        }
    }


    private static <T> void invokeCallBack(Class<? extends T> clazz, T t) {
        if (CollectionUtils.isNotEmpty(clazz.getInterfaces()) && Arrays.asList(clazz.getInterfaces()).contains(SingletonCallBack.class)) {
            SingletonCallBack callBack = (SingletonCallBack) t;
            callBack.invokeCallBack(t);
        }
    }
}
